package edu.neu.cs6515.board;

import lombok.Getter;
import edu.neu.cs6515.util.Number;

/**
 * Represents the deltas for the distance and angle to a hexagons neighbor
 * for each sector on the board.
 *
 * Example: Hexagon 3/2 in Sector 0 has the deltas:
 *      - bottomDist = -1
 *      - bottomAng = 0
 *      - bottomLeftDist = -1
 *      - bottomLeftAng = -1
 *      - bottomRightDist = 0
 *      - bottomRightAng = 1
 *      - topDist = 1
 *      - topAng = 0
 *      - topLeftDist = 0
 *      - topLeftAng = -1
 *      - topRightDist = 1
 *      - topRightAng = 1
 *
 *      to the neighboring hexagons in each direction
 *
 *      ==> The correct delta for a hexagon is returned by produceDelta(Hexagon h)
 *
 *            /|\
 *   SECTOR5 / | \ SECTOR0
 *          /  |  \
 *         |\  |  /|
 *         | \ | / |
 * SECTOR4 |  \|/  | SECTOR1
 *         |  /|\  |
 *         | / | \ |
 *         |/  |  \|
 *          \  |  /
 *   SECTOR3 \ | / SECTOR2
 *            \|/
 *
 *  A SECTORX_HEX_IN_BORDER represents the hexagons on the first border of a sector. Example:
 *
 *         +\
 *         + \ SECTOR0
 *         +  \
 *         +  /
 *         + /
 *         +/
 *
 * */
public final class DistanceAngleDelta {

    private static final DistanceAngleDelta CENTER = new DistanceAngleDelta(1, 3, 1, 4, 1, 2, 1, 0, 1,
            5, 1, 1);

    private static final DistanceAngleDelta SECTOR0_HEX_IN_BORDER = new DistanceAngleDelta(-1, 0, 0, -1, 0, 1, 1, 0, 1,
            -1, 1, 1);

    private static final DistanceAngleDelta SECTOR0 = new DistanceAngleDelta(-1, 0, -1, -1, 0, 1, 1, 0,
            0, -1, 1, 1);

    private static final DistanceAngleDelta SECTOR1_HEX_IN_BORDER = new DistanceAngleDelta(0, 1, -1, -1, 1, 2, 1, 0, 0,
            -1, 1, 1);

    private static final DistanceAngleDelta SECTOR1 = new DistanceAngleDelta(0, 1, -1, -1, 1, 2, 0, -1,
            -1, -2, 1, 1);

    private static final DistanceAngleDelta SECTOR2_HEX_IN_BORDER = new DistanceAngleDelta(1, 3, 0, 1, 1, 2, 0, -1, -1,
            -2, 1, 1);

    private static final DistanceAngleDelta SECTOR2 = new DistanceAngleDelta(1, 3, 0, 1, 1, 2, -1, -3,
            -1, -2, 0, -1);

    private static final DistanceAngleDelta SECTOR3_HEX_IN_BORDER = new DistanceAngleDelta(1, 3, 1, 4, 1, 2, -1, -3, 0,
            1, 0, -1);

    private static final DistanceAngleDelta SECTOR3 = new DistanceAngleDelta(1, 3, 1, 4, 0, -1, -1, -3,
            0, 1, -1, -4);

    private static final DistanceAngleDelta SECTOR4_HEX_IN_BORDER = new DistanceAngleDelta(1, 3, 1, 4, 0, -1, 0, 1, 1,
            5, -1, -4);

    private static final DistanceAngleDelta SECTOR4 = new DistanceAngleDelta(0, -1, 1, 4, -1, -5, 0, 1,
             1, 5, -1, -4);

    private static final DistanceAngleDelta SECTOR5_HEX_IN_BORDER = new DistanceAngleDelta(0, -1, 1, 4, -1, -5, 1, 6,
            1, 5, 0, 1);

    private static final DistanceAngleDelta SECTOR5 = new DistanceAngleDelta(-1, -6, 0, -1, -1, -5, 1, 6,
            1, 5, 0, 1);

    @Getter
    private final int bottomDist;
    @Getter
    private final int bottomAng;
    @Getter
    private final int bottomLeftDist;
    @Getter
    private final int bottomLeftAng;
    @Getter
    private final int bottomRightDist;
    @Getter
    private final int bottomRightAng;
    @Getter
    private final int topDist;
    @Getter
    private final int topAng;
    @Getter
    private final int topLeftDist;
    @Getter
    private final int topLeftAng;
    @Getter
    private final int topRightDist;
    @Getter
    private final int topRightAng;

    /**
     * Private c'tor to make sure, users of this class only use the predifined instances
     *
     * @param bottomDist delta to bottomDist
     * @param bottomAng delta to bottomAng
     * @param bottomLeftDist delta to bottomLeftDist
     * @param bottomLeftAng delta to bottomLeftAng
     * @param bottomRightDist delta to bottomRightDist
     * @param bottomRightAng delta to bottomRightAng
     * @param topDist delta to topDist
     * @param topAng delta to topAng
     * @param topLeftDist delta to topLeftDist
     * @param topLeftAng delta to topLeftAng
     * @param topRightDist delta to topRightDist
     * @param topRightAng delta to topRightAng
     */
    // CHECKSTYLE:OFF
    private DistanceAngleDelta(final int bottomDist, final int bottomAng,
            final int bottomLeftDist, final int bottomLeftAng, final int bottomRightDist,
            final int bottomRightAng, final int topDist, final int topAng, final int topLeftDist,
            final int topLeftAng, final int topRightDist, final int topRightAng) {
     // CHECKSTYLE:ON
        this.bottomDist = bottomDist;
        this.bottomAng = bottomAng;
        this.bottomLeftDist = bottomLeftDist;
        this.bottomLeftAng = bottomLeftAng;
        this.bottomRightDist = bottomRightDist;
        this.bottomRightAng = bottomRightAng;
        this.topDist = topDist;
        this.topAng = topAng;
        this.topLeftDist = topLeftDist;
        this.topLeftAng = topLeftAng;
        this.topRightDist = topRightDist;
        this.topRightAng = topRightAng;
    }

    /**
     * Returns the matching delta information according to the
     * side of the given hexagon
     * @param hex Hexagon whose delta to neighbor is supposed to be determined
     * @return Distance and angle delta info for the neighbors on each side
     */
    public static DistanceAngleDelta produceDelta(final Hexagon hex) {
        if (hex.getDistance() == 0) {
            return CENTER;
        }
        final double sector = (double) hex.getAngle() / hex.getDistance();
        if (Number.sameAs(sector, 0)) {
            return DistanceAngleDelta.SECTOR0_HEX_IN_BORDER;
        } else if (sector > 0 && sector < 1) {
            return DistanceAngleDelta.SECTOR0;
        } else if (Number.sameAs(sector, 1)) {
            return DistanceAngleDelta.SECTOR1_HEX_IN_BORDER;
        } else if (sector > 1 && sector < 2) {
            return DistanceAngleDelta.SECTOR1;
        } else if (Number.sameAs(sector, 2)) {
            return DistanceAngleDelta.SECTOR2_HEX_IN_BORDER;
        } else if (sector > 2 && sector < 3) {
            return DistanceAngleDelta.SECTOR2;
        } else if (Number.sameAs(sector, 3)) {
            return DistanceAngleDelta.SECTOR3_HEX_IN_BORDER;
        } else if (sector > 3 && sector < 4) {
            return DistanceAngleDelta.SECTOR3;
        } else if (Number.sameAs(sector, 4)) {
            return DistanceAngleDelta.SECTOR4_HEX_IN_BORDER;
        } else if (sector > 4 && sector < 5) {
            return DistanceAngleDelta.SECTOR4;
        } else if (Number.sameAs(sector, 5)) {
            return DistanceAngleDelta.SECTOR5_HEX_IN_BORDER;
        } else if (sector > 5 && sector < 6) {
            return DistanceAngleDelta.SECTOR5;
        }
        throw new IllegalArgumentException("Hexagon is invalid!");
    }
}
